﻿using System;
using System.Collections.Generic;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using System.Numerics;
using SpiceNet.Parameters;

namespace SpiceNet.Models
{
    /// <summary>
    /// An instance of a mutual inductance
    /// </summary>
    public class MutualInductance : Instance
    {
        /// <summary>
        /// Get a list of parameters
        /// </summary>
        public override Dictionary<string, IP> ParamTable => _pTable;

        /// <summary>
        /// Private variables
        /// </summary>
        private Inductor ind1, ind2;
        private double MUTfactor;

        #region Parameters
        private enum _c
        {
            MUT_COEFF = 401,
            MUT_IND1 = 402,
            MUT_IND2 = 403,
            MUT_COEFF_SENS = 404,
            MUT_QUEST_SENS_REAL = 601,
            MUT_QUEST_SENS_IMAG = 602,
            MUT_QUEST_SENS_MAG = 603,
            MUT_QUEST_SENS_PH = 604,
            MUT_QUEST_SENS_CPLX = 605,
            MUT_QUEST_SENS_DC = 606
        }
        private static Dictionary<string, IP> _pTable = new Dictionary<string, IP>
        {
            { "k", new IP(IF.IOPAP, (int)_c.MUT_COEFF, T.REAL, "Mutual inductance") },
            { "coefficient", new IP(IF.IOPR, (int)_c.MUT_COEFF, T.REAL, "") },
            { "inductor1", new IP(IF.IOP, (int)_c.MUT_IND1, T.INSTANCE, "First coupled inductor") },
            { "inductor2", new IP(IF.IOP, (int)_c.MUT_IND2, T.INSTANCE, "Second coupled inductor") },
            { "sens_coeff", new IP(IF.IP, (int)_c.MUT_COEFF_SENS, T.FLAG, "flag to request sensitivity WRT coupling factor") },
            { "sens_dc", new IP(IF.OPU, (int)_c.MUT_QUEST_SENS_DC, T.REAL, "dc sensitivity ") },
            { "sens_real", new IP(IF.OPU, (int)_c.MUT_QUEST_SENS_REAL, T.REAL, "real part of ac sensitivity") },
            { "sens_imag", new IP(IF.OPU, (int)_c.MUT_QUEST_SENS_IMAG, T.REAL, "dc sensitivity and imag part of ac sensitivty") },
            { "sens_mag", new IP(IF.OPU, (int)_c.MUT_QUEST_SENS_MAG, T.REAL, "sensitivity of AC magnitude") },
            { "sens_ph", new IP(IF.OPU, (int)_c.MUT_QUEST_SENS_PH, T.REAL, "sensitivity of AC phase") },
            { "sens_cplx", new IP(IF.OPU, (int)_c.MUT_QUEST_SENS_CPLX, T.COMPLEX, "ac sensitivity") }
        };

        /// <summary>
        /// The mutual inductance
        /// </summary>
        public Parameter<double> MUTcoupling { get; } = new Parameter<double>();
        public string MUTindName1 { get; set; }
        public string MUTindName2 { get; set; }
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        public MutualInductance(string name) : base(name, 0, typeof(InductorModel)) { }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">The name of the mutual inductance</param>
        /// <param name="ind1">The name of inductor 1</param>
        /// <param name="ind2">The name of inductor 2</param>
        /// <param name="coupling">The coupling coefficient</param>
        public MutualInductance(string name, string ind1, string ind2, double coupling) : this(name)
        {
            MUTindName1 = ind1;
            MUTindName2 = ind2;
            MUTcoupling.Par(coupling);
        }

        /// <summary>
        /// Set a parameter
        /// </summary>
        protected override void Param(int id, object value, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.MUT_COEFF:
                    MUTcoupling.Par((double)value);
                    break;
                case _c.MUT_IND1:
                    MUTindName1 = (string)value;
                    break;
                case _c.MUT_IND2:
                    MUTindName2 = (string)value;
                    break;
                case _c.MUT_COEFF_SENS:
                    // MUTsenParmNo = value->iValue;
                    break;
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Get a parameter
        /// </summary>
        protected override object Ask(int id, Circuit ckt = null)
        {
            switch ((_c)id)
            {
                case _c.MUT_COEFF:
                    return MUTfactor;
                case _c.MUT_IND1:
                    return MUTindName1;
                case _c.MUT_IND2:
                    return MUTindName2;
                /* case _c.MUT_QUEST_SENS_DC:
                    if (ckt.CKTsenInfo)
                    {
                        return *(ckt.CKTsenInfo->SEN_Sap[select->iValue + 1] +
                                MUTsenParmNo);
                    }
                case _c.MUT_QUEST_SENS_REAL:
                    if (ckt.CKTsenInfo)
                    {
                        return *(ckt.CKTsenInfo->SEN_RHS[select->iValue + 1] +
                                MUTsenParmNo);
                    }
                case _c.MUT_QUEST_SENS_IMAG:
                    if (ckt.CKTsenInfo)
                    {
                        return *(ckt.CKTsenInfo->SEN_iRHS[select->iValue + 1] +
                                MUTsenParmNo);
                    }
                case _c.MUT_QUEST_SENS_MAG:
                    if (ckt.CKTsenInfo)
                    {
                        vr = *(ckt.CKTrhsOld + select->iValue + 1);
                        vi = *(ckt.CKTirhsOld + select->iValue + 1);
                        vm = sqrt(vr * vr + vi * vi);
                        if (vm == 0)
                        {
                            return 0;
                        }
                        sr = *(ckt.CKTsenInfo->SEN_RHS[select->iValue + 1] +
                                MUTsenParmNo);
                        si = *(ckt.CKTsenInfo->SEN_iRHS[select->iValue + 1] +
                                MUTsenParmNo);
                        return (vr * sr + vi * si) / vm;
                    }
                case _c.MUT_QUEST_SENS_PH:
                    if (ckt.CKTsenInfo)
                    {
                        vr = *(ckt.CKTrhsOld + select->iValue + 1);
                        vi = *(ckt.CKTirhsOld + select->iValue + 1);
                        vm = vr * vr + vi * vi;
                        if (vm == 0)
                        {
                            return 0;
                        }
                        sr = *(ckt.CKTsenInfo->SEN_RHS[select->iValue + 1] +
                                MUTsenParmNo);
                        si = *(ckt.CKTsenInfo->SEN_iRHS[select->iValue + 1] +
                                MUTsenParmNo);
                        return (vr * si - vi * sr) / vm;
                    }
                case _c.MUT_QUEST_SENS_CPLX:
                    if (ckt.CKTsenInfo)
                    {
                        value->cValue.real =
                                *(ckt.CKTsenInfo->SEN_RHS[select->iValue + 1] +
                                MUTsenParmNo);
                        value->cValue.imag =
                                *(ckt.CKTsenInfo->SEN_iRHS[select->iValue + 1] +
                                MUTsenParmNo);
                    } */
                default:
                    throw new BadParameterException();
            }
        }

        /// <summary>
        /// Set up the mutual inductance
        /// </summary>
        /// <param name="ckt"></param>
        public override void Setup(Circuit ckt, Model model, object p = null)
        {
            // Find the inductors
            Model[] models = ckt.FindModels(typeof(InductorModel));

            // Find the inductors we need
            for (int i = 0; i < models.Length; i++)
            {
                if (ind1 == null)
                    ind1 = (Inductor)models[i].Find(MUTindName1);
                if (ind2 == null)
                    ind2 = (Inductor)models[i].Find(MUTindName2);
            }
            if (ind1 == null)
                throw new CircuitException(String.Format("{0}: Could not find inductor {1}", Name, MUTindName1));
            if (ind2 == null)
                throw new CircuitException(String.Format("{0}: Could not find inductor {1}", Name, MUTindName2));
        }

        /// <summary>
        /// Do temperature-dependent calculations
        /// </summary>
        /// <param name="ckt"></param>
        public override void Temperature(Circuit ckt, Model model, object p = null)
        {
            // Calculate the internal coupling factor
            MUTfactor = MUTcoupling * Math.Sqrt(ind1.INDinduct * ind2.INDinduct);
        }

        /// <summary>
        /// Load the circuit with the mutual inductance
        /// </summary>
        /// <param name="ckt"></param>
        public override void Load(Circuit ckt, Model model, object p = null)
        {
            int pass = (int)p;

            // Only execute during the second pass
            if (pass != 1)
                return;

            CircuitState state = ckt.State;
            if ((ckt.Mode & (Circuit.Modes.Dc | Circuit.Modes.InitPred)) == 0)
            {
                state.States[0][ind1.FluxState] += MUTfactor * state.Solution[ind2.INDibr];
                state.States[0][ind2.FluxState] += MUTfactor * state.Solution[ind1.INDibr];
            }

            state.Matrix[ind1.INDibr, ind2.INDibr] -= MUTfactor * ckt.Integration.Deriv;
            state.Matrix[ind2.INDibr, ind1.INDibr] -= MUTfactor * ckt.Integration.Deriv;
        }

        /// <summary>
        /// Load the circuit with the mutual inductance for AC analysis
        /// </summary>
        /// <param name="ckt"></param>
        public override void AcLoad(Circuit ckt, Model model, object p = null)
        {
            Complex val = ckt.StateComplex.Omega * MUTfactor;
            ckt.StateComplex.Matrix[ind1.INDibr, ind2.INDibr] -= val;
            ckt.StateComplex.Matrix[ind2.INDibr, ind1.INDibr] -= val;
        }
    }
}
